using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Aima.Core.util
{
    public static class Consts
    {
        public static double Epsilon = 0.00001;
    }
    public class Util<T>
    {
        public const String No = "No";
        public const String Yes = "Yes";


        private static readonly Random r = new Random();


        public T First(List<T> l)
        {
            return l.First();
        }


        public List<T> Rest(List<T> l)
        {
            return l.Skip(1).ToList();
        }

        public static bool RandomBoolean()
        {
            int trueOrFalse = r.Next(2);
            return (trueOrFalse != 0);
        }

        public static double[] Normalize(double[] probDist)
        {
            int len = probDist.Length;
            double total = probDist.Aggregate(0.0, (current, d) => current + d);

            var normalized = new double[len];
            if (Math.Abs(total - 0) > Consts.Epsilon)
            {
                for (int i = 0; i < len; i++)
                {
                    normalized[i] = probDist[i]/total;
                }
            }

            return normalized;
        }

        public static List<Double> Normalize(List<Double> values)
        {
            var valuesAsArray = new double[values.Count()];
            for (int i = 0; i < valuesAsArray.Length; i++)
            {
                valuesAsArray[i] = values[i];
            }
            double[] normalized = Normalize(valuesAsArray);
            return normalized.ToList();
        }

        public static int Min(int i, int j)
        {
            return (i > j ? j : i);
        }

        public static int Max(int i, int j)
        {
            return (i < j ? j : i);
        }

        public static int Max(int i, int j, int k)
        {
            return Max(Max(i, j), k);
        }

        public static int Min(int i, int j, int k)
        {
            return Min(Min(i, j), k);
        }


        public T SelectRandomlyFromList(List<T> l)
        {
            return l[r.Next(l.Count())];
        }


        public T Mode(List<T> l)
        {
            var hash = new Dictionary<T, int>();
            foreach (T obj in l)
            {
                if (hash.ContainsKey(obj))
                {
                    hash.Add(obj, hash[obj] + 1);
                }
                else
                {
                    hash.Add(obj, 1);
                }
            }

            T maxkey = hash.Keys.Last();
            foreach (T key in  hash.Keys)
            {
                if (hash[key] > hash[maxkey])
                {
                    maxkey = key;
                }
            }
            return maxkey;
        }

        public static String[] YesNo()
        {
            return new[] {Yes, No};
        }

        public static double Log2(double d)
        {
            return Math.Log(d)/Math.Log(2);
        }

        public static double Information(double[] probabilities)
        {
            return probabilities.Sum(d => (-1.0*Log2(d)*d));
        }


        public List<T> RemoveFrom(List<T> list, T member)
        {
            var newList = new List<T>(list);
            newList.Remove(member);
            return newList;
        }


        public double SumOfSquares(IEnumerable<double> list)
        {
            if (list == null) throw new ArgumentNullException("list");
            return list.Aggregate<double, double>(0, (current, item) => current + (item*item));
        }

        public static String Ntimes(String s, int n)
        {
            var buf = new StringBuilder();
            for (int i = 0; i < n; i++)
            {
                buf.Append(s);
            }
            return buf.ToString();
        }

        public static void CheckForNanOrInfinity(double d)
        {
            if (Double.IsNaN(d))
            {
                throw new Exception("Not a Number");
            }
            if (Double.IsInfinity(d))
            {
                throw new Exception("Infinite Number");
            }
        }

        public static int RandomNumberBetween(int i, int j)
        {
            /* i,j bothinclusive */
            return r.Next(j - i + 1) + i;
        }

        public static double CalculateMean(List<Double> lst)
        {
            Double sum = lst.Aggregate(0.0, (current, d) => current + d);
            return sum/lst.Count();
        }

        public static double CalculateStDev(List<Double> values, double mean)
        {
            int listSize = values.Count();

            Double sumOfDiffSquared = values.Select(value => value - mean).Select(diffFromMean => ((diffFromMean*diffFromMean)/(listSize - 1))).Sum();
            double variance = sumOfDiffSquared;
            // (listSize - 1);
            // assumes at least 2 members in list.
            return Math.Sqrt(variance);
        }

        public static List<Double> NormalizeFromMeanAndStdev(List<Double> values,
                                                             double mean, double stdev)
        {
            return values.Select(d => (d - mean)/stdev).ToList();
        }

        public static double GenerateRandomDoubleBetween(double lowerLimit,
                                                         double upperLimit)
        {
            return lowerLimit + ((upperLimit - lowerLimit)*r.NextDouble());
        }
    }
}